
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using Xunit.Extensions;


namespace Boilen.Primitives.Members {

    public class TestGuardMember {

        [Fact]
        public void constructor_fails_for_null_doc( ) {
            Doc nullDoc = null;
            string guardName = "name";
            string valueName = "value name";
            Type exceptionType = typeof( ArgumentException );
            string docSummary = "summary";

            Assert.Throws<ArgumentNullException>( ( ) => new Guard( nullDoc, guardName, valueName, exceptionType, docSummary ) );
        }

        [Fact]
        public void constructor_fails_for_null_guardName( ) {
            Doc doc = CreateDoc( );
            string nullGuardName = null;
            string valueName = "value name";
            Type exceptionType = typeof( ArgumentException );
            string docSummary = "summary";

            Assert.Throws<ArgumentNullException>( ( ) => new Guard( doc, nullGuardName, valueName, exceptionType, docSummary ) );
        }

        [Fact]
        public void constructor_fails_for_empty_guardName( ) {
            Doc doc = CreateDoc( );
            string emptyGuardName = "";
            string valueName = "value name";
            Type exceptionType = typeof( ArgumentException );
            string docSummary = "summary";

            Assert.Throws<ArgumentException>( ( ) => new Guard( doc, emptyGuardName, valueName, exceptionType, docSummary ) );
        }

        [Fact]
        public void constructor_fails_for_null_valueName( ) {
            Doc doc = CreateDoc( );
            string guardName = "name";
            string nullValueName = null;
            Type exceptionType = typeof( ArgumentException );
            string docSummary = "summary";

            Assert.Throws<ArgumentNullException>( ( ) => new Guard( doc, guardName, nullValueName, exceptionType, docSummary ) );
        }

        [Fact]
        public void constructor_fails_for_empty_valueName( ) {
            Doc doc = CreateDoc( );
            string guardName = "name";
            string emptyValueName = "";
            Type exceptionType = typeof( ArgumentException );
            string docSummary = "summary";

            Assert.Throws<ArgumentException>( ( ) => new Guard( doc, guardName, emptyValueName, exceptionType, docSummary ) );
        }

        [Fact]
        public void constructor_fails_for_null_exceptionType( ) {
            Doc doc = CreateDoc( );
            string guardName = "name";
            string valueName = "value name";
            Type nullExceptionType = null;
            string docSummary = "summary";

            Assert.Throws<ArgumentNullException>( ( ) => new Guard( doc, guardName, valueName, nullExceptionType, docSummary ) );
        }

        [Fact]
        public void constructor_fails_for_null_docSummary( ) {
            Doc doc = CreateDoc( );
            string guardName = "name";
            string valueName = "value name";
            Type exceptionType = typeof( ArgumentException );
            string nullDocSummary = null;

            Assert.Throws<InvalidOperationException>( ( ) => new Guard( doc, guardName, valueName, exceptionType, nullDocSummary ) );
        }

        [Fact]
        public void constructor_fails_for_empty_docSummary( ) {
            Doc doc = CreateDoc( );
            string guardName = "name";
            string valueName = "value name";
            Type exceptionType = typeof( ArgumentException );
            string emptyDocSummary = "";

            Assert.Throws<InvalidOperationException>( ( ) => new Guard( doc, guardName, valueName, exceptionType, emptyDocSummary ) );
        }

        [Fact]
        public void constructor_succeeds_for_valid_values( ) {
            Doc doc = CreateDoc( );
            string guardName = "name";
            string valueName = "value name";
            Type exceptionType = typeof( ArgumentException );
            string docSummary = "summary";
            int expectedPriority = 0;

            var g = new Guard( doc, guardName, valueName, exceptionType, docSummary );

            Assert.Equal( g.GuardName, guardName );
            Assert.Equal( g.ValueName, valueName );
            Assert.Equal( g.ExceptionType, exceptionType );
            Assert.Equal( g.Description, docSummary );
            Assert.Equal( g.Priority, expectedPriority );
        }


        [Fact]
        public void ChangeValueName_fails_for_null_collection( ) {
            IEnumerable<Guard> nullCollection = null;
            string valueName = "value";

            Assert.Throws<ArgumentNullException>( ( ) => Guard.ChangeValueName( nullCollection, valueName ) );
        }

        [Fact]
        public void ChangeValueName_fails_for_null_valueName( ) {
            IEnumerable<Guard> collection = new Guard[0];
            string nullValueName = null;

            Assert.Throws<ArgumentNullException>( ( ) => Guard.ChangeValueName( collection, nullValueName ) );
        }

        [Fact]
        public void ChangeValueName_fails_for_empty_valueName( ) {
            IEnumerable<Guard> collection = new Guard[0];
            string emptyValueName = "";

            Assert.Throws<ArgumentException>( ( ) => Guard.ChangeValueName( collection, emptyValueName ) );
        }

        [Fact]
        public void ChangeValueName_succeeds_for_valid_arguments( ) {
            Doc doc = CreateDoc( );
            var guard = new Guard( doc, "name", "valueName", typeof( Exception ), "description" );
            guard.Arguments.Add( "item" );
            guard.Priority = 5;
            var guards = new[] { guard };
            string newValueName = "value";

            var newGuards = Guard.ChangeValueName( guards, newValueName );

            Assert.Equal( newGuards.Length, guards.Length );

            var newGuard = newGuards[0];
            Assert.NotSame( newGuard, guard );
            Assert.Equal( newGuard.ValueName, newValueName );
            Assert.Equal( newGuard.GuardName, guard.GuardName );
            Assert.Equal( newGuard.ExceptionType, guard.ExceptionType );
            Assert.Equal( newGuard.Description, guard.Description );
            Assert.Equal( newGuard.Priority, guard.Priority );
            AssertExtensions.EqualCollection( newGuard.Arguments, guard.Arguments );
        }


        [Theory]
        [InlineData( "GuardMethod", 0 )]
        [InlineData( "GuardMethod", 1 )]
        [InlineData( "GuardMethod", 2 )]
        [InlineData( "GuardMethod", 3 )]
        public void Write_succeeds_for_valid_arguments( string guardName, int argumentCount ) {
            Doc doc = CreateDoc( );
            string valueName = "valueName";
            Type exceptionType = typeof( Exception );
            string docSummary = "summary";
            string argumentString = Enumerable.Range( 0, argumentCount )
                .Aggregate( "", ( s, i ) => (i == 0 ? "" : s + ", ") + "arg" + i );
            string[] expectedLines = new[] { "." + guardName + "(" + argumentString + ")" };
            var writer = new StringBuilderCodeWriter( );

            var g = new Guard( doc, guardName, valueName, exceptionType, docSummary );
            for( int i = 0; i < argumentCount; ++i ) {
                g.Arguments.Add( "arg" + i );
            }

            g.Write( writer );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Fact]
        public void Write_succeeds_for_NotNull( ) {
            Doc doc = CreateDoc( );
            string valueName = "valueName";
            string[] expectedLines = new[] { ".NotNull()" };
            var g = Guard.NotNull( doc, valueName, false );
            var writer = new StringBuilderCodeWriter( );

            g.Write( writer );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Fact]
        public void Write_succeeds_for_EnumIsDefined( ) {
            Doc doc = CreateDoc( );
            string valueName = "valueName";
            string[] expectedLines = new[] { ".EnumIsDefined()" };
            var g = Guard.EnumIsDefined( doc, valueName, false );
            var writer = new StringBuilderCodeWriter( );

            g.Write( writer );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Fact]
        public void Write_succeeds_for_NotSpecialValue( ) {
            Doc doc = CreateDoc( );
            string valueName = "valueName";
            string[] expectedLines = new[] { ".NotSpecialValue()" };
            var g = Guard.NotSpecialValue( doc, valueName, false );
            var writer = new StringBuilderCodeWriter( );

            g.Write( writer );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Fact]
        public void Write_succeeds_for_IsInRange( ) {
            Doc doc = CreateDoc( );
            string valueName = "valueName";
            string description = "description";
            string condition = "condition";
            string format = "format";
            string[] expectedLines = new[] { string.Format( ".IsInRange({0}, \"{1}\")", condition, format ) };
            var g = Guard.IsInRange( doc, valueName, description, condition, format );
            var writer = new StringBuilderCodeWriter( );

            g.Write( writer );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Fact]
        public void Write_succeeds_for_Satisfies( ) {
            Doc doc = CreateDoc( );
            string valueName = "valueName";
            string description = "description";
            string condition = "condition";
            string format = "format";
            string[] args = new[] { "arg1", "arg2" };
            string[] expectedLines = new[] { string.Format( ".Satisfies({0}, \"{1}\", {2})", condition, format, string.Join( ", ", args ) ) };
            var g = Guard.Satisfies( doc, valueName, description, condition, format, args );
            var writer = new StringBuilderCodeWriter( );

            g.Write( writer );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }


        [Fact]
        public void WriteGuards_fails_for_null_writer( ) {
            ICodeWriter nullWriter = null;
            IEnumerable<Guard> guards = new Guard[0];

            Assert.Throws<ArgumentNullException>( ( ) => Guard.WriteGuards( nullWriter, guards ) );
        }

        [Fact]
        public void WriteGuards_fails_for_null_collection( ) {
            ICodeWriter writer = new StringBuilderCodeWriter( );
            IEnumerable<Guard> nullGuards = null;

            Assert.Throws<ArgumentNullException>( ( ) => Guard.WriteGuards( writer, nullGuards ) );
        }

        [Fact]
        public void WriteGuards_succeeds_for_empty_collection( ) {
            string[] expectedLines = StringBuilderCodeWriter.EmptyLines;
            var writer = new StringBuilderCodeWriter( );
            IEnumerable<Guard> guards = new Guard[0];

            Guard.WriteGuards( writer, guards );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Theory]
        [InlineData( true, 1 )]
        [InlineData( true, 2 )]
        [InlineData( false, 1 )]
        [InlineData( false, 2 )]
        public void WriteGuards_succeeds_for_guard_collection_with_single_value( bool areParameterGuards, int guardCount ) {
            Doc doc = CreateDoc( );
            string guardName = "Guard";
            string valueName = "value";
            Type exceptionType = typeof( Exception );
            string docSummary = "summary";
            string firstLine = valueName + ".Guard" + (areParameterGuards ? "Param" : "Value") + "(\"" + valueName + "\")";
            string[] expectedLines = Enumerable.Range( 0, guardCount + 1 )
                .Select( i => i == 0 ? firstLine : "    ." + guardName + (i - 1) + "()" + (i == guardCount ? ";" : "") )
                .Concat( new[] { "", "" } )
                .ToArray( );
            var guards = Enumerable.Range( 0, guardCount )
                .Select( i => new Guard( doc, guardName + i, valueName, exceptionType, docSummary ) { IsValueGuard = !areParameterGuards } )
                .ToArray( );
            var writer = new StringBuilderCodeWriter( );

            Guard.WriteGuards( writer, guards );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Fact]
        public void WriteGuards_succeeds_for_guard_collection_with_multiple_values( ) {
            const int valueCount = 2;
            const int guardCount = 2;
            Doc doc = CreateDoc( );
            string guardName = "Guard";
            string valueName = "value";
            Type exceptionType = typeof( Exception );
            string docSummary = "summary";
            string firstLineFormat = "{0}.GuardValue(\"{0}\")";

            var expectedLines = new List<string>( );
            var guards = new List<Guard>( );
            for( int i = 0; i < valueCount; ++i ) {
                expectedLines.Add( string.Format( firstLineFormat, valueName + i ) );
                for( int j = 0; j < guardCount; ++j ) {
                    guards.Add( new Guard( doc, guardName + j, valueName + i, exceptionType, docSummary ) { IsValueGuard = true } );

                    string guardLineSuffix = j + 1 == guardCount ? ";" : "";
                    expectedLines.Add( "    ." + guardName + j + "()" + guardLineSuffix );
                }
            }
            expectedLines.Add( "" );
            expectedLines.Add( "" );
            var writer = new StringBuilderCodeWriter( );


            Guard.WriteGuards( writer, guards );


            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }

        [Fact]
        public void WriteGuards_succeeds_for_parameterized_guards( ) {
            Doc doc = CreateDoc( );
            string guardName = "Guard";
            string valueName = "value";
            Type exceptionType = typeof( Exception );
            string docSummary = "summary";
            var guards = new[] {
                new Guard( doc, guardName + 0, valueName + 0, exceptionType, docSummary ),
                new Guard( doc, guardName + 1, valueName + 1, exceptionType, docSummary ),
                new Guard( doc, guardName + 2, valueName + 0, exceptionType, docSummary ) { Priority = 1 }
            };
            string[] expectedLines = new[] {
                "value0.GuardParam(\"value0\")",
                "    .Guard2()",
                "    .Guard0();",
                "value1.GuardParam(\"value1\")",
                "    .Guard1();",
                "",
                ""
            };
            var writer = new StringBuilderCodeWriter( );

            Guard.WriteGuards( writer, guards );

            string[] lines = writer.GetLines( );
            AssertExtensions.EqualCollection( lines, expectedLines );
        }


        private static Doc CreateDoc( ) {
            return new Doc( "fqn", "name", "type", "parent type" );
        }

    }

}
